package main

import (
	"fmt"
	"jvm/classpath"
	"jvm/instructions"
	"jvm/instructions/base"
	"jvm/rtda"
	"jvm/rtda/heap"
	. "jvm/typedef"
	"strings"
)

type JVM struct {
	cmd         *Cmd
	classLoader *heap.ClassLoader
	mainThread  *rtda.Thread
}

// *******************************************************
func newJVM(cmd *Cmd) *JVM {
	classpath := classpath.NewClassPath(cmd.XjreOption, cmd.cpOption)
	loader := heap.NewClassLoader(classpath, cmd.verboseClassFlag)
	return &JVM{
		cmd:         cmd,
		classLoader: loader,
		mainThread:  rtda.NewThread(),
	}
}

func (self *JVM) start() {
	self.initVM()
	self.execMain()
}

func (self *JVM) initVM() {
	vmClass := self.classLoader.LoadClass("sun/misc/VM")
	base.InitClass(self.mainThread, vmClass)
	interpret(self.mainThread, self.cmd.verboseInstFlag)
}

func (self *JVM) execMain() {
	className := strings.Replace(self.cmd.class, ".", "/", -1)
	class := self.classLoader.LoadClass(className)
	mainMethod := class.GetStaticMethod("main", "([Ljava/lang/String;)V")
	if mainMethod == nil {
		fmt.Printf("Main method not found in class %s\n", self.cmd.class)
		return
	}
	args := createArgsArray(self.classLoader, self.cmd.args)
	frame := self.mainThread.NewFrame(mainMethod)
	frame.LocalVars().SetRef(0, args)
	self.mainThread.PushFrame(frame)
	interpret(self.mainThread, self.cmd.verboseInstFlag)
}

// 创建主函数的参数数组对象
func createArgsArray(loader *heap.ClassLoader, args []string) *heap.Object {
	arrClass := loader.LoadClass("[Ljava/lang/String;")
	arr := arrClass.NewArray(U4(len(args)))
	refs := arr.Refs()
	for i, _ := range refs {
		jStr := heap.JString(loader, args[i])
		refs[i] = jStr
	}
	return arr
}

// *******************************************************
// 解释器

func interpret(thread *rtda.Thread, logInst bool) {
	defer catchErr(thread)
	reader := base.NewCoderReader()
	for {
		frame := thread.CurrentFrame()
		thread.SetPC(frame.NextPC())
		code := frame.Method().Code()
		reader.Reset(code, thread.PC())
		instCode := reader.ReadU1()
		inst := instructions.NewInst(instCode)
		inst.ReadOperands(reader)
		frame.SetNextPC(reader.PC())
		if logInst {
			logInstruction(frame, inst)
		}
		inst.Execute(frame)
		if thread.IsStackEmpty() {
			break
		}
	}

}
func logInstruction(frame *rtda.Frame, inst base.Inst) {
	method := frame.Method()
	className := method.Class().Name()
	methodName := method.Name()
	fmt.Printf("%v.%v #%2d %T %v\n", className, methodName, frame.Thread().PC(), inst, inst)
}

func catchErr(thread *rtda.Thread) {
	if r := recover(); r != nil {
		logFrames(thread)
		panic(r)
	}
}
func logFrames(thread *rtda.Thread) {
	for !thread.IsStackEmpty() {
		frame := thread.PopFrame()
		method := frame.Method()
		className := method.Class().Name()
		fmt.Printf(">>pc:%4d %v.%v%v \n",
			frame.NextPC(), className, method.Name(), method.Descriptor())
	}
}
